/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.services;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.concurrency.IEntityLock;
import org.apereo.portal.concurrency.IEntityLockService;
import org.apereo.portal.concurrency.IEntityLockServiceFactory;
import org.apereo.portal.concurrency.LockingException;
import org.apereo.portal.properties.PropertiesManager;

/**
 * This is a bootstrap class and facade for the IEntityLockService implementation. It presents a
 * simple api for acquiring lock objects, <code>IEntityLocks</code>, that can be used to control
 * concurrent access to portal entities in a multi-server environment. (See
 * org.apereo.portal.concurrency.IEntityLockService for a fuller description.)
 *
 * <p>Currently supported lock types are IEntityLockService.READ_LOCK and
 * IEntityLockService.WRITE_LOCK.
 *
 * <p>If I want to lock an entity for update, I ask the service for a write lock:
 *
 * <p><code>
 *       Class type = anEntity.getClass(); // maybe hard-coded(?)<br>
 *       String key = anEntity.getKey();<br>
 *       EntityIdentifier ei = new EntityIdentifier(key, type);<br>
 *       String owner = getThePortalUserId();<br>
 *       IEntityLock lock = EntityLockService.instance().newWriteLock(ei, owner);<br>
 * </code>
 *
 * <p>Or maybe:
 *
 * <p><code>
 *       IEntityLock lock = EntityLockService.instance().newWriteLock(ei, owner, duration);<br>
 * </code>
 *
 * <p>If there are no conflicting locks on the entity, the service returns the requested lock. If I
 * acquire the lock, I know that no other client will be able to get a conflicting lock, and from
 * then on, I communicate with the service via the lock:
 *
 * <p><code>
 *   lock.convert(int newType); // See IEntityLockService for types.<br>
 *   lock.isValid();<br>
 *   lock.release();<br>
 *   lock.renew();<br>
 * </code>
 *
 * <p>A READ lock guarantees shared access; other clients can get READ locks but not WRITE locks. A
 * WRITE lock guarantees exclusive access; no other clients can get either READ or WRITE locks on
 * the entity.
 */
public class EntityLockService {

    private static final Log log = LogFactory.getLog(EntityLockService.class);

    // Singleton instance of the bootstrap class:
    private static EntityLockService instance = null;
    // The lock service:
    private IEntityLockService lockService = null;
    /** Creates new EntityLockService */
    private EntityLockService() throws LockingException {
        super();
        initialize();
    }
    /** @exception LockingException */
    private void initialize() throws LockingException {
        String eMsg = null;
        String factoryName =
                PropertiesManager.getProperty(
                        "org.apereo.portal.concurrency.IEntityLockServiceFactory", null);

        if (factoryName == null) {
            log.warn(
                    "Property org.apereo.portal.concurrency.IEntityLockServiceFactory not configured in PropertiesManager.  Defaulting to  org.apereo.portal.concurrency.locking.ReferenceEntityLockServiceFactory");
            factoryName = "org.apereo.portal.concurrency.locking.ReferenceEntityLockServiceFactory";
        }

        try {
            IEntityLockServiceFactory lockServiceFactory =
                    (IEntityLockServiceFactory) Class.forName(factoryName).newInstance();
            lockService = lockServiceFactory.newLockService();
        } catch (Exception e) {
            eMsg = "EntityLockService.initialize(): Problem creating entity lock service...";
            log.error(eMsg, e);
            throw new LockingException(eMsg, e);
        }
    }

    public static synchronized EntityLockService instance() throws LockingException {
        if (instance == null) {
            instance = new EntityLockService();
        }
        return instance;
    }
    /**
     * Returns a read lock for the entity type, entity key and owner.
     *
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     * @param entityType Class
     * @param entityKey String
     * @param owner String
     * @exception LockingException
     */
    public IEntityLock newReadLock(Class entityType, String entityKey, String owner)
            throws LockingException {
        return lockService.newLock(entityType, entityKey, IEntityLockService.READ_LOCK, owner);
    }
    /**
     * Returns a read lock for the entity type, entity key and owner.
     *
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     * @param entityType Class
     * @param entityKey String
     * @param owner String
     * @param duration int (in seconds)
     * @exception LockingException
     */
    public IEntityLock newReadLock(Class entityType, String entityKey, String owner, int duration)
            throws LockingException {
        return lockService.newLock(
                entityType, entityKey, IEntityLockService.READ_LOCK, owner, duration);
    }
    /**
     * Returns a read lock for the <code>IBasicEntity</code> and owner.
     *
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     * @param entityID EntityIdentifier
     * @param owner String
     * @exception LockingException
     */
    public IEntityLock newReadLock(EntityIdentifier entityID, String owner)
            throws LockingException {
        return lockService.newLock(
                entityID.getType(), entityID.getKey(), IEntityLockService.READ_LOCK, owner);
    }
    /**
     * Returns a read lock for the <code>IBasicEntity</code>, owner and duration.
     *
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     * @param entityID EntityIdentifier
     * @param owner String
     * @param durationSecs int
     * @exception LockingException
     */
    public IEntityLock newReadLock(EntityIdentifier entityID, String owner, int durationSecs)
            throws LockingException {
        return lockService.newLock(
                entityID.getType(),
                entityID.getKey(),
                IEntityLockService.READ_LOCK,
                owner,
                durationSecs);
    }
    /**
     * Returns a write lock for the entity type, entity key and owner.
     *
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     * @param entityType Class
     * @param entityKey String
     * @param owner String
     * @exception LockingException
     */
    public IEntityLock newWriteLock(Class entityType, String entityKey, String owner)
            throws LockingException {
        return lockService.newLock(entityType, entityKey, IEntityLockService.WRITE_LOCK, owner);
    }
    /**
     * Returns a write lock for the entity type, entity key and owner.
     *
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     * @param entityType Class
     * @param entityKey String
     * @param owner String
     * @param durationSecs int
     * @exception LockingException
     */
    public IEntityLock newWriteLock(
            Class entityType, String entityKey, String owner, int durationSecs)
            throws LockingException {
        return lockService.newLock(
                entityType, entityKey, IEntityLockService.WRITE_LOCK, owner, durationSecs);
    }
    /**
     * Returns a write lock for the <code>IBasicEntity</code> and owner.
     *
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     * @param entityID EntityIdentifier
     * @param owner String
     * @exception LockingException
     */
    public IEntityLock newWriteLock(EntityIdentifier entityID, String owner)
            throws LockingException {
        return lockService.newLock(
                entityID.getType(), entityID.getKey(), IEntityLockService.WRITE_LOCK, owner);
    }
    /**
     * Returns a write lock for the <code>IBasicEntity</code>, owner and duration.
     *
     * @return org.apereo.portal.concurrency.locking.IEntityLock
     * @param entityID EntityIdentifier
     * @param owner String
     * @param durationSecs int
     * @exception LockingException
     */
    public IEntityLock newWriteLock(EntityIdentifier entityID, String owner, int durationSecs)
            throws LockingException {
        return lockService.newLock(
                entityID.getType(),
                entityID.getKey(),
                IEntityLockService.WRITE_LOCK,
                owner,
                durationSecs);
    }
}
